package han.chensing.latos.common.level.content;

import han.chensing.latos.common.level.content.objects.CommonBPM;
import han.chensing.latos.common.level.content.objects.CommonLine;
import han.chensing.latos.common.level.content.objects.CommonObject;
import han.chensing.latos.common.level.meta.LatosMetaData;

import java.io.Serial;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.function.Predicate;

public class LevelData implements Serializable,Iterable<CommonObject>{
    @Serial
    private static final long serialVersionUID = -7658338933627426758L;

    public static List<Field> listFields;
    static {
        Class<LevelData> levelDataClass = LevelData.class;
        listFields=new ArrayList<>();
        try {
            listFields.add(levelDataClass.getDeclaredField("commonLines"));
            listFields.add(levelDataClass.getDeclaredField("reservedObjects"));
            listFields.add(levelDataClass.getDeclaredField("userObjects"));
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Stores lines
     */
    private ArrayList<CommonLine> commonLines;

    /**
     * Reserved object is something like BPM, combo object, name of song, etc.
     */
    private ArrayList<CommonObject> reservedObjects;

    /**
     * Like images, custom fonts, etc..
     */
    private ArrayList<CommonObject> userObjects;


    public LevelData(){
        this.commonLines=new ArrayList<>();
        this.reservedObjects=new ArrayList<>();
        this.userObjects=new ArrayList<>();
        for(int i=0;i<20;i++)
            commonLines.add(new CommonLine(String.format(Locale.getDefault(),"Line%d",i)));
    }

    public void init(LatosMetaData latosMetaData){
        reservedObjects.add(new CommonBPM(latosMetaData.getOffset()));
    }

    public ArrayList<CommonLine> getLineObjects() {
        return commonLines;
    }

    public void setLines(ArrayList<CommonLine> commonLines) {
        this.commonLines = commonLines;
    }

    public ArrayList<CommonObject> getReservedObjects() {
        return reservedObjects;
    }

    public void setReservedObjects(ArrayList<CommonObject> reservedObjects) {
        this.reservedObjects = reservedObjects;
    }

    public ArrayList<CommonObject> getUserObjects() {
        return userObjects;
    }

    public void setUserObjects(ArrayList<CommonObject> userObjects) {
        this.userObjects = userObjects;
    }

    public CommonObject findReservedObject(Predicate<CommonObject> predicate){
        for (CommonObject next : reservedObjects) {
            if (predicate.test(next))return next;
        }
        return null;
    }

    @Override
    public Iterator<CommonObject> iterator() {
        return new ObjectFullIterator();
    }

    private class ObjectFullIterator implements Iterator<CommonObject> {
        int fieldIndex=0;
        int itemIndex=0;
        @SuppressWarnings("unchecked")
        protected boolean hasNext(int fieldIndexI,int itemIndexI){
            try{
                if (fieldIndexI>=listFields.size()) return false;
                Field field = listFields.get(fieldIndexI);
                ArrayList<CommonObject> commonObjects = (ArrayList<CommonObject>) field.get(LevelData.this);
                if (commonObjects==null) return false;
                if (itemIndexI>=commonObjects.size()){
                    return hasNext(fieldIndexI+1,0);
                }
                return true;
            }catch (Exception ignore){
                return false;
            }
        }

        @Override
        public boolean hasNext() {
            return hasNext(fieldIndex,itemIndex);
        }

        @SuppressWarnings("unchecked")
        @Override
        public CommonObject next() {
            try{
                if (fieldIndex>=listFields.size()) return null;
                Field field = listFields.get(fieldIndex);
                ArrayList<CommonObject> commonObjects = (ArrayList<CommonObject>) field.get(LevelData.this);
                if (commonObjects==null) return null;
                if (itemIndex>=commonObjects.size()){
                    fieldIndex++;
                    itemIndex=0;
                    return next();
                }
                return commonObjects.get(itemIndex++);
            }catch (Exception ignore){
                return null;
            }
        }
    }
}
